
from PIL import Image
import numpy as np
import os
def crop_resize_save(image_path, save_path, resize=(224, 224), crop_threshold=25):
    """
    打开 image_path 的图像，裁剪掉黑边，然后 resize 到目标尺寸，
    并保存为 PNG 格式到 save_path。
    
    参数：
        image_path (str): 输入图像文件路径。
        save_path (str): 输出图像保存路径（PNG 格式）。
        resize (tuple): 目标尺寸，默认 (224, 224)。
        crop_threshold (int): 裁剪时的黑边阈值，默认 25。
    
    返回：
        dict: 包含裁剪信息的字典：
              {
                  'crop_box': (top, bottom, left, right),
                  'original_size': (height, width),
                  'cropped_size': (cropped_height, cropped_width)
              }
    """
    image = Image.open(image_path).convert("RGB")
    if image_path.lower().endswith('.ppm'):
        image = image.convert("RGB")
    image_array = np.array(image)
    original_size = [int(image_array.shape[0]), int(image_array.shape[1])]
    
    # 使用带信息的裁剪函数
    cropped_array, crop_box = crop_black_border(image_array, threshold=crop_threshold,image_name=os.path.basename(save_path))
    cropped_size = [int(cropped_array.shape[0]), int(cropped_array.shape[1])]
    crop_box = [int(x) for x in crop_box]# 确保 crop_box 内的值也是 Python 原生类型

    # hotfix: 有时候info计算逻辑错误，导致之前save过一次，因此为了加速，不进行保存,谨慎起见，可以废除
    if os.path.exists(save_path) and False:
    # if os.path.exists(save_path):
        return {
            'crop_box': crop_box,
            'original_size': original_size,
            'cropped_size': cropped_size
        }
    # 转换为 Image 对象进行 resize 操作
    cropped_image = Image.fromarray(cropped_array)

    resized_image = cropped_image.resize(resize)
    try:
        resized_image.verify()
    except Exception as e:
        print(image_path)
        print(save_path)
        print(crop_box)
        raise ValueError(f"Resized image verification failed for {save_path}: {e}")
    resized_image.save(save_path, format="PNG")

    
    info = {
        'crop_box': crop_box,
        'original_size': original_size,
        'cropped_size':cropped_size
    }
    return info

def crop_black_border(image_array, threshold=25, tolerance_ratio=0.01, image_name=""):
    """
    裁剪图像的黑边部分，允许一定比例的噪点。

    参数：
        image_array (np.array): 输入图像数组。
        threshold (int): 灰度阈值，默认 25。
        tolerance_ratio (float): 容忍亮点比例（例如 0.01 表示允许1%的像素高于阈值）。
        image_name (str): 图像名，仅用于调试。

    返回：
        tuple: (裁剪后的图像数组, (top, bottom, left, right))
    """
    if image_array.ndim == 3:
        gray = np.mean(image_array, axis=2)
    else:
        gray = image_array

    h, w = gray.shape

    # 行/列黑度判断：允许少量高于阈值的点
    row_bright_ratio = np.mean(gray > threshold, axis=1)  # 每行中亮点比例
    col_bright_ratio = np.mean(gray > threshold, axis=0)  # 每列中亮点比例

    rows_black = row_bright_ratio <= tolerance_ratio
    cols_black = col_bright_ratio <= tolerance_ratio

    non_black_rows = np.where(~rows_black)[0]
    non_black_cols = np.where(~cols_black)[0]

    if non_black_rows.size == 0 or non_black_cols.size == 0:
        print(f"⚠️ {image_name}: 全图接近黑色或检测失败，返回原图。")
        return image_array, (0, h, 0, w)

    top, bottom = non_black_rows[0], non_black_rows[-1] + 1
    left, right = non_black_cols[0], non_black_cols[-1] + 1

    cropped_height = bottom - top
    cropped_width = right - left

    # 如果裁剪比例太小，认为误裁
    if cropped_width < 0.5 * w or cropped_height < 0.7 * h:
        print(f"⚠️ {image_name}: 裁剪过小，忽略（{cropped_width}×{cropped_height} vs {w}×{h}）")
        return image_array, (0, h, 0, w)

    return image_array[top:bottom, left:right], (top, bottom, left, right)


def crop_resize_save_with_info(image_path, save_path, info, resize=(224, 224)):
    """
    打开 image_path 的图像，裁剪掉黑边后返回裁剪信息，并将 resize 后的图像保存为 PNG 格式到 save_path。
    
    参数：
        image_path (str): 输入图像文件路径。
        info: 主图像的信息，包含原图尺寸和裁剪框 (top, bottom, left, right)。
        save_path (str): 输出图像保存路径（PNG 格式）。
        resize (tuple): 目标尺寸，默认 (224, 224)。
        crop_threshold (int): 裁剪时黑边阈值，默认 25。
    
    返回：
        None
    """
    # 打开图像并检查尺寸是否匹配
    if isinstance(image_path, str):
        image = Image.open(image_path)
    else:
        if isinstance(image_path,Image.Image):
            image = image_path
        else:
            raise ValueError("image_path should be a file path or PIL Image.")

    image_array = np.array(image)
    # 检查info的合法性
    info_size=info['original_size']
    image_size=[int(image_array.shape[0]), int(image_array.shape[1])]
    if info_size[0]!=image_size[0] or info_size[1]!=image_size[1]:
        # resize 到info_size
        image = image.resize((info_size[1],info_size[0])) 
        image_array = np.array(image)
        print(f"⚠️ {os.path.basename(save_path)}: 图像尺寸与 info 不匹配，已 resize 到 info 尺寸。")
    # 从 info 中提取原图尺寸和裁剪框
    original_height, original_width = image_array.shape[0], image_array.shape[1]
    top, bottom, left, right = info['crop_box']

    # 如果原图的尺寸与 info 提供的尺寸不同，抛出错误
    if original_height != info['original_size'][0] or original_width != info['original_size'][1]:
        raise ValueError(f"Image size mismatch: {original_height}x{original_width} != "
                         f"{info['original_size'][0]}x{info['original_size'][1]}.")

    # 按照提供的裁剪框进行裁剪
    cropped_image_array = image_array[top:bottom, left:right]

    # 如果裁剪后图像大小小于目标尺寸，则抛出异常
    # cropped_height, cropped_width = cropped_image_array.shape[0], cropped_image_array.shape[1]
    # if cropped_width < 0.6 * original_width or cropped_height < 0.7 * original_height:
    #     raise ValueError(f"Cropped image dimensions are too small: cropped_width={cropped_width} "
    #                      f"(min required {0.6 * original_width}), cropped_height={cropped_height} "
    #                      f"(min required {0.7 * original_height}).")

    # 进行 resize 操作
    cropped_image = Image.fromarray(cropped_image_array)
    try:
        resample_filter = Image.Resampling.LANCZOS
    except AttributeError:
        resample_filter = Image.LANCZOS
    resized_image = cropped_image.resize(resize, resample_filter)

    # 保存为 PNG 格式
    resized_image.save(save_path, format="PNG")
def resize_save(image_path, save_path, resize):
    """
    打开 image_path 对应的图片，调整大小为 resize 指定的尺寸，
    然后保存到 save_path（PNG 格式）。

    参数：
        image_path (str): 输入图像路径。
        save_path (str): 输出图像保存路径。
        resize (tuple): 目标尺寸 (宽，高)。
    """
    image = Image.open(image_path)
    try:
        resample_filter = Image.Resampling.LANCZOS
    except AttributeError:
        resample_filter = Image.LANCZOS
    resized_image = image.resize(resize, resample_filter)
    resized_image.save(save_path, format="PNG")
